﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using NUnit.Framework;
using UnitOfWorkTest.Mocks;
using UnitOfWork;

namespace UnitOfWorkTest
{
    [TestFixture]
    public class EFUnitOfWorkTest
    {        
        IUnitOfWork Source { get; set; }
                
        [SetUp]
        public void Init()
        {
            Database.SetInitializer<MockDbContext>(new MockDbInitializer());

            Source = new EFUnitOfWork<MockDbContext>();
        }

        [TearDown]
        public void CleanUp()
        {
            Assert.IsFalse(Source.IsConnectionOpen);
        }

        [Test]
        public void Insert() 
        {
            //check if connection is not established until is necessary
            Assert.IsFalse(Source.IsConnectionOpen);

            //testing method
            Source.Insert<Employee>(new Employee { Name = "TestEmp", Surname = "Test", Salary = 2000, Rank = 10 });

            //check if connection is open after first need
            Assert.IsTrue(Source.IsConnectionOpen);

            Source.SaveChanges(true);

            //check if Insert added desire object
            using (var dbContext = new MockDbContext())
            {
                Assert.IsNotNull(dbContext.Employees.Where(q => q.Rank == 10).SingleOrDefault());
            }
        }

        [Test]
        public void Update() 
        {
            Employee entity;

            //get entity from original DBContext
            using (var dbContext = new MockDbContext())
            {
                entity = dbContext.Employees.First();                
            }

            entity.Name = "TestUpdate";

            //update entity through UoW
            Source.Update<Employee>(entity);
            Source.SaveChanges(true);

            //check if entity was updated
            using (var dbContext = new MockDbContext())
            {
                Assert.IsTrue(dbContext.Employees.First().Name == "TestUpdate");
            }

            //check if adding identical elements cause only update it without exception
            //needed to check if library can be use in f# with immutable style
            //entity = Source.Get<Employee>().First();
            //var updatedEntity = new Employee { ID = entity.ID, 
            //    EmployerID = entity.EmployerID, 
            //    Name = entity.Name, 
            //    Rank = entity.Rank, 
            //    Salary = entity.Salary, 
            //    Surname = "TestUpdate2" 
            //};

            //Source.Update<Employee>(updatedEntity);
            //Source.SaveChanges(true);

            ////check if entity was updated
            //using (var dbContext = new MockDbContext())
            //{
            //    Assert.IsTrue(dbContext.Employees.First().Surname == "TestUpdate2");
            //}
        }

        [Test]
        public void GetById() 
        { 
            //get target from original DBContext
            Employee target;
            using (var dbContext = new MockDbContext())
            {
                target = dbContext.Employees.First();
            }

            //get employee fromUoW
            var testEmployee = Source.GetById<Employee>(target.ID);

            //check if object from UoW was getting and is the same as object from original DbContext
            Assert.AreEqual(target.Name + target.Surname, testEmployee.Name + testEmployee.Surname);

            //check if getting not existing object return null
            Assert.IsNull(Source.GetById<Employee>(int.MaxValue));

            //close coneection
            Source.Dispose();
        }

        [Test]
        public void Get() 
        { 
            //check if parameterless get returns all elements
            using (var context = new MockDbContext())
            {
                Assert.AreEqual(context.Employees.Count(), Source.Get<Employee>().ToList().Count());
            }

            //check if getting one object include objecets conncted with its
            Assert.IsNotNull(Source.Get<Employer>().First().Employees);

            Source.Dispose();
        }

        [Test]
        public void GetByFilters() 
        {
            //check get with filtering
            Assert.AreEqual(Source.Get<Employee>(q => q.Surname == "E2").Single().Surname, "E2");

            //check get with sorting sorting
            Assert.AreEqual(Source.Get<Employee>(orderBy: q => q.OrderBy(e => e.Rank)).First().Surname, "TestName");

            //check if get returns empty list when none entity fulfil filter
            Assert.AreEqual(Source.Get<Employee>(q => q.ID == 1000).ToList().Count, 0);

            Source.Dispose();
        }

        [Test]
        public void DeleteById() 
        {
            Source.DeleteById<Employee>(1);
            Source.SaveChanges();

            //check if collection of emplyee down in original DbContext
            //and if exactly object of id = 1 is deleting
            using (var context = new MockDbContext())
            {
                Assert.AreEqual(2, context.Employees.Count());
                Assert.IsNull(context.Employees.Where(q => q.ID == 1).SingleOrDefault());
            }

            Source.Dispose();
        }

        [Test]
        public void Join()
        {
            //check if joining emplyee(id:3) with employer(id:1) return Rank = 5
            Assert.AreEqual(5, Source.Join<Employer, Employee, int>(q => q.Name, p => p.Surname, (q, p) => p.Rank).First());

            //check if impossible joining get null value
            Assert.AreEqual(0, Source.Join<Employer, Employee, int>(q => q.Name, p => p.Name, (q, p) => p.Rank).FirstOrDefault());

            Source.Dispose();
        }

        [Test]
        public void LeftJoin()
        {            
            //check if joining emplyees with employer(id:1) return Rank = 6
            Assert.AreEqual(6, Source.LeftJoin<Employer, Employee, TestJoinEntity>(q => q.ID, p => p.EmployerID, (q, p) =>new TestJoinEntity { Name = q.Name, Rank = p.Rank}).First().Rank);
            
            //check if impossible joining get null value for joined value
            Assert.AreEqual(0, Source.LeftJoin<Employer, Employee, TestJoinEntity>(q => q.Name, p => p.Name, (q, p) =>new TestJoinEntity { Name = q.Name, Rank = (p != null) ? p.Rank : 0}).First().Rank);

            //check if impossible joining get value for object to which join was performed
            Assert.IsNotNull(Source.LeftJoin<Employer, Employee, TestJoinEntity>(q => q.Name, p => p.Name, (q, p) => new TestJoinEntity { Name = q.Name, Rank = (p != null) ? p.Rank : 0 }).First().Name);

            Source.Dispose();
        }

        [Test]
        public void Query()
        {
            //check if query closed connection !most important
            Assert.IsFalse(Source.IsConnectionOpen);
            
            Source.Query(() => {
                Assert.IsNotNull(Source.Get<Employer>(q => q.ID == 1).SingleOrDefault());
                Assert.GreaterOrEqual(Source.Get<Employee>().Count(), 1);
                Source.Update<Employee>(Source.Get<Employee>().First());
            });

            //check if query closed connection !most important
            Assert.IsFalse(Source.IsConnectionOpen);
        }
    }

    class TestJoinEntity
    {
        public string Name { get; set; }
        public int Rank { get; set; }
    }
}
